package ace.module.common.core.impl.manager.impl;

import ace.cmp.core.constants.CoreConstant;
import ace.cmp.data.spring.data.jdbc.querydsl.rsql.core.manager.spring.AbstractSpringDtoManager;
import ace.cmp.spring.data.jdbc.querydsl.core.QuerydslRelationHelper;
import ace.module.common.core.impl.annotation.appout.AppId;
import ace.module.common.core.impl.manager.EntityOfAppManager;
import ace.module.common.core.impl.manager.helper.ManagerHelper;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Ops;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.SimpleExpression;
import com.querydsl.sql.RelationalPathBase;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.SneakyThrows;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

/**
 * 应用接口.
 *
 * @author Caspar
 */
public abstract class AbstractEntityOfAppManager<TEntity, IdT, TEntityDto>
    extends AbstractSpringDtoManager<TEntity, IdT, TEntityDto>
    implements EntityOfAppManager<TEntity, IdT, TEntityDto>, InitializingBean {

  @Autowired
  @Getter(AccessLevel.PROTECTED)
  private ManagerHelper managerHelper;

  @Autowired
  @Getter(AccessLevel.PROTECTED)
  private QuerydslRelationHelper querydslRelationHelper;

  @Getter(AccessLevel.PROTECTED)
  private RelationalPathBase<TEntity> entityPath;

  @Getter(AccessLevel.PROTECTED)
  private Path primaryKeyPath;

  @Getter(AccessLevel.PROTECTED)
  private SimpleExpression primaryKeyExpression;

  @Getter(AccessLevel.PROTECTED)
  private Field appIdField;

  @Getter(AccessLevel.PROTECTED)
  private SimpleExpression appIdExpression;

  @Getter(AccessLevel.PROTECTED)
  private Field deleteFlagField;

  @Getter(AccessLevel.PROTECTED)
  private SimpleExpression deleteFlagExpression;

  @Override
  public void afterPropertiesSet() throws Exception {
    super.afterPropertiesSet();
    this.entityPath = this.querydslRelationHelper.getRelationalPathBaseByEntityType(this.getEntityClass());
    this.appIdField = this.managerHelper.findAppIdField(this.getEntityClass());
    this.appIdExpression = this.managerHelper.findExpressionByAnnClassOrFieldName(this.getEntityClass(), AppId.class, this.appIdField.getName());
    this.deleteFlagField = this.querydslRelationHelper.getLogicDeleteField(this.getEntityClass());
    this.deleteFlagExpression = this.querydslRelationHelper.getLogicDeleteExpression(this.getEntityClass());
    this.primaryKeyExpression = this.querydslRelationHelper.getPrimaryKeyExpression(this.getEntityClass());
    this.primaryKeyPath = this.querydslRelationHelper.getPrimaryKeyPath(this.getEntityClass());
  }

  @SneakyThrows
  @Override
  public void setEntityAppId(TEntity entity, Serializable appId) {
    PropertyUtils.setProperty(entity, this.appIdField.getName(), appId);
  }

  @SneakyThrows
  @Override
  public Serializable getEntityAppId(TEntity entity) {
    return (Serializable) PropertyUtils.getProperty(entity, this.appIdField.getName());
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public boolean removeByAppIdAndIdIn(Serializable appId, List<IdT> ids) {
    Assert.notNull(appId, "appId can not be null");
    Assert.notEmpty(ids, "ids can not be empty");

    Predicate predicate = ExpressionUtils.allOf(
        this.getPrimaryKeyExpression().in(ids),
        this.getAppIdExpression().eq(appId)
    );
    return this.remove(predicate);
  }

  @Transactional(rollbackFor = Exception.class)
  @Override
  public boolean removeLogicByAppIdIdAndIdIn(Serializable appId, List<IdT> ids) {
    Assert.notNull(appId, "appId can not be null");
    Assert.notEmpty(ids, "ids can not be empty");

    Predicate predicate = ExpressionUtils.allOf(
        this.getPrimaryKeyExpression().in(ids),
        this.getAppIdExpression().eq(appId)
    );
    return this.removeLogic(predicate);
  }

  @Override
  public TEntity getByAppIdAndId(Serializable appId, IdT id) {
    if (Objects.isNull(appId) || Objects.isNull(id)) {
      return null;
    }
    Predicate predicate = ExpressionUtils.allOf(
        this.getPrimaryKeyExpression().eq(id),
        this.getAppIdExpression().eq(appId),
        this.getDeleteFlagExpression().eq(CoreConstant.DELETE_FLAG_NOT)
    );
    return this.getOne(predicate);
  }

  @Override
  public List<TEntity> getByAppIdAndIds(Serializable appId, List<IdT> ids) {
    if (Objects.isNull(appId) || CollectionUtils.isEmpty(ids)) {
      return new ArrayList<>();
    }
    Predicate predicate = ExpressionUtils.allOf(
        this.getPrimaryKeyExpression().in(ids),
        this.getAppIdExpression().eq(appId),
        this.getDeleteFlagExpression().eq(CoreConstant.DELETE_FLAG_NOT)
    );
    return this.getList(predicate);
  }

  @Override
  public Map<IdT, TEntity> getMapByAppIdAndIds(Serializable appId, List<IdT> ids) {
    if (Objects.isNull(appId) || CollectionUtils.isEmpty(ids)) {
      return new HashMap<>();
    }
    Predicate predicate = ExpressionUtils.allOf(
        this.getPrimaryKeyExpression().in(ids),
        this.getAppIdExpression().eq(appId),
        this.getDeleteFlagExpression().eq(CoreConstant.DELETE_FLAG_NOT)
    );
    return this.getMap(predicate);
  }

  @Override
  public Page<TEntity> searchByAppIdAndRsql(Serializable appId, String rsql, Pageable pageable) {
    Predicate rsqlPredicate = this.parseRsql(rsql);
    Predicate predicate = ExpressionUtils.allOf(
        Expressions.predicate(Ops.WRAPPED, rsqlPredicate),
        this.getAppIdExpression().eq(appId),
        this.getDeleteFlagExpression().eq(CoreConstant.DELETE_FLAG_NOT)
    );

    return this.search(predicate, pageable);
  }
}
